"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_1 = require("vscode-languageserver/node");
const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
const fs = __importStar(require("fs"));
// Create a connection for the server
const connection = (0, node_1.createConnection)(node_1.ProposedFeatures.all);
// Cache for parsed file contents
const fileCache = new Map();
// Watch for file changes
connection.onDidChangeWatchedFiles((params) => {
    for (const change of params.changes) {
        const filePath = change.uri.toString().replace('file://', '');
        if (fileCache.has(filePath)) {
            fileCache.delete(filePath);
            connection.console.log(`Cache invalidated for ${filePath}`);
        }
    }
});
// Create a simple text document manager
const documents = new node_1.TextDocuments(vscode_languageserver_textdocument_1.TextDocument);
// Mapper to store Java <-> XML mappings
const mapperMappings = new Map();
const xmlMappings = new Map();
connection.onInitialize((params) => {
    return {
        capabilities: {
            textDocumentSync: node_1.TextDocumentSyncKind.Incremental,
            // Tell the client that the server supports code lens
            definitionProvider: true
        }
    };
});
// Parse Java Mapper files and store method mappings
connection.onDidOpenTextDocument((params) => {
    const doc = documents.get(params.textDocument.uri);
    if ((doc === null || doc === void 0 ? void 0 : doc.languageId) === 'java') {
        parseJavaMapper(doc);
    }
});
// Parse XML Mapper files and store statement mappings
connection.onDidOpenTextDocument((params) => {
    const doc = documents.get(params.textDocument.uri);
    if ((doc === null || doc === void 0 ? void 0 : doc.languageId) === 'xml') {
        parseXmlMapper(doc);
    }
});
// Handle jump to definition requests
connection.onDefinition((params) => {
    try {
        const doc = documents.get(params.textDocument.uri);
        if (!doc)
            return [];
        if (doc.languageId === 'java') {
            // Jump from Java to XML
            const methodName = getMethodNameAtPosition(doc, params.position);
            if (methodName && mapperMappings.has(methodName)) {
                const xmlUri = mapperMappings.get(methodName);
                if (xmlUri) {
                    // Try to find exact position in XML
                    const xmlPath = xmlUri.toString().replace('file://', '');
                    if (fs.existsSync(xmlPath)) {
                        const xmlContent = fs.readFileSync(xmlPath, 'utf8');
                        const methodTag = `<select id="${methodName}"`;
                        const methodPos = xmlContent.indexOf(methodTag);
                        if (methodPos >= 0) {
                            const lines = xmlContent.substr(0, methodPos).split('\n');
                            const line = lines.length - 1;
                            const character = lines[lines.length - 1].length;
                            return [node_1.Location.create(xmlUri, {
                                    start: { line, character },
                                    end: { line, character: character + methodName.length }
                                })];
                        }
                    }
                    return [node_1.Location.create(xmlUri, {
                            start: { line: 0, character: 0 },
                            end: { line: 0, character: 0 }
                        })];
                }
            }
        }
        else if (doc.languageId === 'xml') {
            // Jump from XML to Java
            const statementId = getStatementIdAtPosition(doc, params.position);
            if (statementId && xmlMappings.has(statementId)) {
                const javaUri = xmlMappings.get(statementId);
                if (javaUri) {
                    // Try to find exact position in Java
                    const javaPath = javaUri.toString().replace('file://', '');
                    if (fs.existsSync(javaPath)) {
                        const javaContent = fs.readFileSync(javaPath, 'utf8');
                        const methodRegex = new RegExp(`\\b${statementId}\\s*\\(`);
                        const methodPos = javaContent.search(methodRegex);
                        if (methodPos >= 0) {
                            const lines = javaContent.substr(0, methodPos).split('\n');
                            const line = lines.length - 1;
                            const character = lines[lines.length - 1].indexOf(statementId);
                            return [node_1.Location.create(javaUri, {
                                    start: { line, character },
                                    end: { line, character: character + statementId.length }
                                })];
                        }
                    }
                    return [node_1.Location.create(javaUri, {
                            start: { line: 0, character: 0 },
                            end: { line: 0, character: 0 }
                        })];
                }
            }
        }
    }
    catch (error) {
        connection.console.error(`Definition provider error: ${error}`);
    }
    return [];
});
// Make the text document manager listen on the connection
// for open, change and close text document events
documents.listen(connection);
// Listen on the connection
connection.listen();
// Helper functions
function parseJavaMapper(doc) {
    const text = doc.getText();
    const uri = doc.uri;
    // Update cache
    fileCache.set(uri.toString().replace('file://', ''), {
        version: doc.version,
        content: text
    });
    // Check if it's a Mapper interface
    if (!text.includes('@Mapper') && !text.includes('public interface')) {
        return;
    }
    // Find corresponding XML file
    const xmlPath = uri.toString().replace('.java', '.xml').replace('file://', '');
    if (!fs.existsSync(xmlPath)) {
        return;
    }
    // Parse method definitions
    const methodRegex = /(?:public\s+)?\w+\s+(\w+)\s*\([^)]*\)/g;
    let match;
    while ((match = methodRegex.exec(text)) !== null) {
        const methodName = match[1];
        mapperMappings.set(methodName, uri.replace('.java', '.xml'));
    }
}
function parseXmlMapper(doc) {
    const text = doc.getText();
    const uri = doc.uri;
    // Update cache
    fileCache.set(uri.toString().replace('file://', ''), {
        version: doc.version,
        content: text
    });
    // Parse namespace (Java interface path)
    const namespaceMatch = text.match(/namespace="([^"]+)"/);
    if (!namespaceMatch)
        return;
    const javaPath = namespaceMatch[1].replace(/\./g, '/') + '.java';
    const javaUri = uri.replace('.xml', '.java');
    // Parse SQL statement IDs
    const idRegex = /<(select|insert|update|delete)\s+id="([^"]+)"/g;
    let match;
    while ((match = idRegex.exec(text)) !== null) {
        const statementId = match[2];
        mapperMappings.set(statementId, javaUri);
    }
}
function getMethodNameAtPosition(doc, position) {
    const text = doc.getText();
    const lines = text.split('\n');
    const lineText = lines[position.line];
    // Find method definition line
    const methodRegex = /(?:public\s+)?\w+\s+(\w+)\s*\([^)]*\)/;
    const methodMatch = lineText.match(methodRegex);
    if (methodMatch) {
        return methodMatch[1];
    }
    // Check if cursor is on method name
    const wordRange = doc.getWordRangeAtPosition(position);
    if (wordRange) {
        const word = doc.getText(wordRange);
        // Verify if this word is a method name in the file
        const methodDefRegex = new RegExp(`\\b${word}\\s*\\(`);
        if (methodDefRegex.test(text)) {
            return word;
        }
    }
    return undefined;
}
function getStatementIdAtPosition(doc, position) {
    const text = doc.getText();
    const lines = text.split('\n');
    const lineText = lines[position.line];
    // Find statement ID in current line
    const idRegex = /id="([^"]+)"/;
    const idMatch = lineText.match(idRegex);
    if (idMatch) {
        return idMatch[1];
    }
    // Check if cursor is on statement ID
    const wordRange = doc.getWordRangeAtPosition(position);
    if (wordRange) {
        const word = doc.getText(wordRange);
        // Verify if this word is an ID in the file
        const idDefRegex = new RegExp(`id="${word}"`);
        if (idDefRegex.test(text)) {
            return word;
        }
    }
    return undefined;
}
